﻿namespace NTLib.WPF.Controls
{
    using Attributes;
    using NTLib.Core.Reflection;
    using System;
    using System.ComponentModel;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Threading;

    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="System.Windows.Controls.Control" />
    public class PropertyViewerItem : IDisposable, INotifyPropertyChanged
    {
        #region Fields

        private readonly PropertyInfo _info;
        private readonly bool _isStatic;
        private readonly string _name;
        private readonly bool _isReadOnly;

        private object _source;
        private string _specialKey;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyViewerItem"/> class.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <param name="source">The source.</param>
        public PropertyViewerItem(PropertyInfo info, object source)
        {
            this._info = info;
            this._source = source;
            this._isReadOnly = info.SetMethod == null || !((info.SetMethod.Attributes & MethodAttributes.Public) == MethodAttributes.Public);
            this._isStatic = this._info.GetMethod != null && this._info.GetMethod.IsStatic;
            this._name = info.Name;

            if (info.HasAttr<PropertyExposedAttribute>())
            {
                var exp = info.GetCustomAttribute<PropertyExposedAttribute>();
                if (!string.IsNullOrEmpty(exp.Name))
                    this._name = exp.Name;

                if (!string.IsNullOrEmpty(exp.SpecialKey))
                    this._specialKey = exp.SpecialKey;
            }

            if (source != null && source is INotifyPropertyChanged)
            {
                ((INotifyPropertyChanged)source).PropertyChanged += PropertyViewerItem_PropertyChanged;
            }
        }

        ~PropertyViewerItem()
        {
            Dispose(true);
        }

        #endregion

        #region Property

        /// <summary>
        /// Gets the type of the property.
        /// </summary>
        /// <value>
        /// The type of the property.
        /// </value>
        public Type PropertyType
        {
            get { return this._info.PropertyType; }
        }

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>
        /// The value.
        /// </value>
        public object Value
        {
            get
            {
                if (this._info.CanRead && (this._isStatic || this._source != null))
                    return this._info.GetValue(this._source);
                return null;
            }
            set
            {
                if (!this.IsReadOnly && (this._isStatic || this._source != null))
                    this._info.SetValue(this._source, value);
            }
        }

        /// <summary>
        /// Gets or sets the identifying name of the element. The name provides a reference so that code-behind, such as event handler code, can refer to a markup element after it is constructed during processing by a XAML processor.
        /// </summary>
        public string PropertyName
        {
            get { return this._name; }
        }

        /// <summary>
        /// Gets the special key.
        /// </summary>
        public string SpecialKey
        {
            get { return this._specialKey; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is read only.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is read only; otherwise, <c>false</c>.
        /// </value>
        public bool IsReadOnly
        {
            get { return this._isReadOnly; }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when [property changed].
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Methods

        /// <summary>
        /// Handles the PropertyChanged event of the PropertyViewerItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void PropertyViewerItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (PropertyChanged == null)
                return;

            if (string.IsNullOrEmpty(e.PropertyName) ||
                string.Equals("null", e.PropertyName, StringComparison.InvariantCultureIgnoreCase) ||
                string.Equals(this._info.Name, e.PropertyName, StringComparison.InvariantCultureIgnoreCase))
            {
                //Dispatcher.Invoke(() =>
                //{
                    this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(Value)));
                //});
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(false);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="v"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool v)
        {
            var src = this._source;
            this._source = null;
            if (src != null && src is INotifyPropertyChanged)
            {
                ((INotifyPropertyChanged)src).PropertyChanged -= PropertyViewerItem_PropertyChanged;
            }
        }

        #endregion
    }
}
